/*
 *  linux/kernel/blk_drv/ramdisk.c
 *
 *  Written by Theodore Ts'o, 12/2/91
 */ //Theodore Ts'o 是Linux社区中的著名人物。Linux在世界范围内的流行也有他很大的功劳，早在 Linux 系统刚问世时，他就怀着极大的热情为Linux的发展提供了邮件列表，并在北美洲地区最早设立了Linux的ftp站点，而且至今仍在为用户提供服务。
// 他对Linux做出的最大贡献之一是提出并实现了ext2文件系统。该文件系统现已成为Linux中事实上的标准文件系统。最近他又退出了ext3文件系统，大大提高了文件系统的稳定性和访问效率。为了表示对他的推崇，Linux journal期刊第97期将他作为封面人物，并对他进行了采访。
// 目前，他为 IEM Linux技术中心工作，并从事有关LSB方面的工作
#include <string.h>         // 字符串头文件。主要定义了一些有关字符串操作的嵌入函数。
#include <linux/config.h>   // 内核配置头文件。定义键盘语言和硬盘类型（HD_TYPE）可选项
#include <linux/sched.h>    // 调度程序头文件。定义任务结构 task_struct 初始任务0的数据
#include <linux/fs.h>       // 文件系统文件。定义文件表结构（file，buffer_head，m_inode等）
#include <linux/kernel.h>   // 内核头文件。含有一些内核常用函数的原形定义。
#include <asm/system.h>     // 系统头文件。定义了设置或修改描述符/中断门等的嵌入式汇编宏
#include <asm/segment.h>    // 段操作头文件。定义了有关段寄存器操作的嵌入式汇编函数
#include <asm/memory.h>     // 内存拷贝头文件。含有memcpy其如是汇编宏函数

#define MAJOR_NR 1          // 内存主设备号是 1
#include "blk.h"

char	*rd_start;          // 虚拟盘在内存的起始位置。在52行初始化函数rd_init()中确定
int	rd_length = 0;          // 虚拟盘所占内存大小（字节）
// 执行虚拟盘（ramdisk）读写操作。程序结构与do_rd_request（）类似
void do_rd_request(void)
{
	int	len;
	char	*addr;
    // 检测请求的合法性
	INIT_REQUEST;   // 下面语句取得ramdisk的起始扇区对应的内存起始位置和内存长度 其中 sector<<9表示sector*512，CURRENT定义为（blk_dev[MAJOR_NR].current_request）
	addr = rd_start + (CURRENT->sector << 9);
	len = CURRENT->nr_sectors << 9; // 如果子设备号不为1
	if ((MINOR(CURRENT->dev) != 1) || (addr+len > rd_start+rd_length)) {
		end_request(0);
		goto repeat;
	}
	if (CURRENT-> cmd == WRITE) {   // 若是写命令（write） 则将请求项中缓冲区的内容复制到addr处，长度为len字节
		(void ) memcpy(addr,
			      CURRENT->buffer,
			      len);
	} else if (CURRENT->cmd == READ) {  // 若是读命令（read） 则将addr 开始的内容复制到请求项中缓冲中，长度为len字节
		(void) memcpy(CURRENT->buffer, 
			      addr,
			      len);
	} else                              // 否则显示命令不存在， 死机
		panic("unknown ramdisk-command");
	end_request(1);             // 请求项成功后处理，置更新标志。并继续处理本设备的下一请求项
	goto repeat;
}

/*  // 返回内存虚拟盘 ramdisk 所需的内存量
 * Returns amount of memory which needs to be reserved.
 */ // 虚拟盘初始化函数。确定虚拟盘在内存中的起始地址，长度。并对整个虚拟盘区清零
long rd_init(long mem_start, int length)
{
	int	i;
	char	*cp;

	blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
	rd_start = (char *) mem_start;
	rd_length = length;
	cp = rd_start;
	for (i=0; i < length; i++)
		*cp++ = '\0';
	return(length);
}
// 如果根文件系统设备（root device）是ramdisk的话，则尝试加载它。 root device原先是指向软盘的，我们将它改成指向 ramdisk
/*  // 加载根文件系统到 ramdisk
 * If the root device is the ram disk, try to load it.
 * In order to do this, the root device is originally set to the
 * floppy, and we later change it to be ram disk.
 */
void rd_load(void)
{
	struct buffer_head *bh; // 高速缓冲块头指针
	struct super_block	s;  // 超级块数据结构
	int		block = 256;	/* Start at block 256 */    // 表示根文件系统影像文件在boot盘第256磁盘块开始处
	int		i = 1;          //
	int		nblocks;
	char		*cp;		/* Move pointer */
	
	if (!rd_length)         // 如果 ramdisk 的长度为零 则退出
		return;
	printk("Ram disk: %d bytes, starting at 0x%x\n", rd_length,
		(int) rd_start);    // 显示 ramdisk 的大小以及内存起始位置
	if (MAJOR(ROOT_DEV) != 2)   // 如果此时根文件设备不是软盘，则退出
		return; // 读软盘块 256+1，256,256+2。breada（）用于读取指定的数据块，并标出还需读的块，然后返回含有数据块的缓冲区指针。如果返回NULL，则表示数据块不可读（fs/ubffer.c）。这里block+1是指磁盘上的超级块
	bh = breada(ROOT_DEV,block+1,block,block+2,-1);
	if (!bh) {
		printk("Disk error while looking for ramdisk!\n");
		return;
	}   // 将 s 指向缓冲区中的磁盘超级块 （d_super_block）磁盘中超级块结构
	*((struct d_super_block *) &s) = *((struct d_super_block *) bh->b_data);
	brelse(bh); // （为什么数据没有复制就立刻释放呢？）
	if (s.s_magic != SUPER_MAGIC)   // 如果超级块中魔数不对，则说明不是MINIX文件系统
		/* No ram disk image present, assume normal floppy boot */  // 磁盘中没有 ramdisk 映像文件，退出执行通常的软盘引导
		return; // 块数 = 逻辑块数（区段数）*2（每区段块数的幂）。如果数据块数大于内存中虚拟盘所能容纳的块数，则不能架子啊，显示出错信息并返回。否则显示加载数据块信息。
	nblocks = s.s_nzones << s.s_log_zone_size;
	if (nblocks > (rd_length >> BLOCK_SIZE_BITS)) {
		printk("Ram disk image too big!  (%d blocks, %d avail)\n", 
			nblocks, rd_length >> BLOCK_SIZE_BITS);
		return;
	}
	printk("Loading %d bytes into ram disk... 0000k", 
		nblocks << BLOCK_SIZE_BITS);
	cp = rd_start;      // cp 指向虚拟盘起始处， 然后将磁盘上根系统映像文件复制到虚拟盘上。
	while (nblocks) {
		if (nblocks > 2)    // 如果需读取的块数多余3块则采用超前预读方式读数据块
			bh = breada(ROOT_DEV, block, block+1, block+2, -1);
		else                // 否则就单块读取
			bh = bread(ROOT_DEV, block);
		if (!bh) {
			printk("I/O error on block %d, aborting load\n", 
				block);
			return;
		}
		(void) memcpy(cp, bh->b_data, BLOCK_SIZE);  // 将缓冲区中的数据复制到 cp 处
		brelse(bh);                                 // 释放缓冲区
		printk("\010\010\010\010\010%4dk",i);       // 打印加载块计数值
		cp += BLOCK_SIZE;                           // 虚拟盘指针前移
		block++;
		nblocks--;
		i++;
	}
	printk("\010\010\010\010\010done \n");
	ROOT_DEV=0x0101;                                // 修改 ROOT_DEV使其指向虚拟盘ramdisk
}
